import { useStore } from '@nanostores/react';
import { motion, type HTMLMotionProps, type Variants } from 'framer-motion';
import { computed } from 'nanostores';
import { memo, useCallback, useEffect, useState, useMemo } from 'react';
import { toast } from 'react-toastify';
import { Popover, Transition } from '@headlessui/react';
import { diffLines, type Change } from 'diff';
import { formatDistanceToNow as formatDistance } from 'date-fns';
import { ActionRunner } from '~/lib/runtime/action-runner';
import { getLanguageFromExtension } from '~/utils/getLanguageFromExtension';
import type { FileHistory } from '~/types/actions';
import { DiffView } from './DiffView';
import {
  type OnChangeCallback as OnEditorChange,
  type OnScrollCallback as OnEditorScroll,
} from '~/components/editor/codemirror/CodeMirrorEditor';
import { IconButton } from '~/components/ui/IconButton';
import { PanelHeaderButton } from '~/components/ui/PanelHeaderButton';
import { Slider, type SliderOptions } from '~/components/ui/Slider';
import { workbenchStore, type WorkbenchViewType } from '~/lib/stores/workbench';
import { classNames } from '~/utils/classNames';
import { cubicEasingFn } from '~/utils/easings';
import { renderLogger } from '~/utils/logger';
import { EditorPanel } from './EditorPanel';
import { Preview } from './Preview';
import useViewport from '~/lib/hooks';
import { PushToGitHubDialog } from '~/components/@settings/tabs/connections/components/PushToGitHubDialog';
import Cookies from 'js-cookie';

interface WorkspaceProps {
  chatStarted?: boolean;
  isStreaming?: boolean;
  actionRunner: ActionRunner;
  metadata?: {
    gitUrl?: string;
  };
  updateChatMestaData?: (metadata: any) => void;
}

const viewTransition = { ease: cubicEasingFn };

const sliderOptions: SliderOptions<WorkbenchViewType> = {
  left: {
    value: 'code',
    text: 'Code',
  },
  middle: {
    value: 'diff',
    text: 'Diff',
  },
  right: {
    value: 'preview',
    text: 'Preview',
  },
};

const workbenchVariants = {
  closed: {
    width: 0,
    transition: {
      duration: 0.2,
      ease: cubicEasingFn,
    },
  },
  open: {
    width: 'var(--workbench-width)',
    transition: {
      duration: 0.2,
      ease: cubicEasingFn,
    },
  },
} satisfies Variants;

const FileModifiedDropdown = memo(({
  fileHistory,
  onSelectFile,
}: {
  fileHistory: Record<string, FileHistory>,
  onSelectFile: (filePath: string) => void,
}) => {
  const modifiedFiles = Object.entries(fileHistory);
  const hasChanges = modifiedFiles.length > 0;
  const [searchQuery, setSearchQuery] = useState('');

  const filteredFiles = useMemo(() => {
    return modifiedFiles.filter(([filePath]) =>
      filePath.toLowerCase().includes(searchQuery.toLowerCase())
    );
  }, [modifiedFiles, searchQuery]);

  return (
    <div className="flex items-center gap-2">
      <Popover className="relative">
        {({ open }: { open: boolean }) => (
          <>
            <Popover.Button className="flex items-center gap-2 px-3 py-1.5 text-sm rounded-lg bg-bolt-elements-background-depth-2 hover:bg-bolt-elements-background-depth-3 transition-colors text-bolt-elements-textPrimary border border-bolt-elements-borderColor">
              <span className="font-medium">File Changes</span>
              {hasChanges && (
                <span className="w-5 h-5 rounded-full bg-accent-500/20 text-accent-500 text-xs flex items-center justify-center border border-accent-500/30">
                  {modifiedFiles.length}
                </span>
              )}
            </Popover.Button>
            <Transition
              show={open}
              enter="transition duration-100 ease-out"
              enterFrom="transform scale-95 opacity-0"
              enterTo="transform scale-100 opacity-100"
              leave="transition duration-75 ease-out"
              leaveFrom="transform scale-100 opacity-100"
              leaveTo="transform scale-95 opacity-0"
            >
              <Popover.Panel className="absolute right-0 z-20 mt-2 w-80 origin-top-right rounded-xl bg-bolt-elements-background-depth-2 shadow-xl border border-bolt-elements-borderColor">
                <div className="p-2">
                  <div className="relative mx-2 mb-2">
                    <input
                      type="text"
                      placeholder="Search files..."
                      value={searchQuery}
                      onChange={(e) => setSearchQuery(e.target.value)}
                      className="w-full pl-8 pr-3 py-1.5 text-sm rounded-lg bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor focus:outline-none focus:ring-2 focus:ring-blue-500/50"
                    />
                    <div className="absolute left-2 top-1/2 -translate-y-1/2 text-bolt-elements-textTertiary">
                      <div className="i-ph:magnifying-glass" />
                    </div>
                  </div>

                  <div className="max-h-60 overflow-y-auto">
                    {filteredFiles.length > 0 ? (
                      filteredFiles.map(([filePath, history]) => {
                        const extension = filePath.split('.').pop() || '';
                        const language = getLanguageFromExtension(extension);

                        return (
                          <button
                            key={filePath}
                            onClick={() => onSelectFile(filePath)}
                            className="w-full px-3 py-2 text-left rounded-md hover:bg-bolt-elements-background-depth-1 transition-colors group bg-transparent"
                          >
                            <div className="flex items-center gap-2">
                              <div className="shrink-0 w-5 h-5 text-bolt-elements-textTertiary">
                                {['typescript', 'javascript', 'jsx', 'tsx'].includes(language) && <div className="i-ph:file-js" />}
                                {['css', 'scss', 'less'].includes(language) && <div className="i-ph:paint-brush" />}
                                {language === 'html' && <div className="i-ph:code" />}
                                {language === 'json' && <div className="i-ph:brackets-curly" />}
                                {language === 'python' && <div className="i-ph:file-text" />}
                                {language === 'markdown' && <div className="i-ph:article" />}
                                {['yaml', 'yml'].includes(language) && <div className="i-ph:file-text" />}
                                {language === 'sql' && <div className="i-ph:database" />}
                                {language === 'dockerfile' && <div className="i-ph:cube" />}
                                {language === 'shell' && <div className="i-ph:terminal" />}
                                {!['typescript', 'javascript', 'css', 'html', 'json', 'python', 'markdown', 'yaml', 'yml', 'sql', 'dockerfile', 'shell', 'jsx', 'tsx', 'scss', 'less'].includes(language) && <div className="i-ph:file-text" />}
                              </div>
                              <div className="flex-1 min-w-0">
                                <div className="flex items-center justify-between gap-2">
                                  <div className="flex flex-col min-w-0">
                                    <span className="truncate text-sm font-medium text-bolt-elements-textPrimary">
                                      {filePath.split('/').pop()}
                                    </span>
                                    <span className="truncate text-xs text-bolt-elements-textTertiary">
                                      {filePath}
                                    </span>
                                  </div>
                                  {(() => {
                                    // Calculate diff stats
                                    const { additions, deletions } = (() => {
                                      if (!history.originalContent) return { additions: 0, deletions: 0 };

                                      const normalizedOriginal = history.originalContent.replace(/\r\n/g, '\n');
                                      const normalizedCurrent = history.versions[history.versions.length - 1]?.content.replace(/\r\n/g, '\n') || '';

                                      if (normalizedOriginal === normalizedCurrent) {
                                        return { additions: 0, deletions: 0 };
                                      }

                                      const changes = diffLines(normalizedOriginal, normalizedCurrent, {
                                        newlineIsToken: false,
                                        ignoreWhitespace: true,
                                        ignoreCase: false
                                      });

                                      return changes.reduce((acc: { additions: number; deletions: number }, change: Change) => {
                                        if (change.added) {
                                          acc.additions += change.value.split('\n').length;
                                        }
                                        if (change.removed) {
                                          acc.deletions += change.value.split('\n').length;
                                        }
                                        return acc;
                                      }, { additions: 0, deletions: 0 });
                                    })();

                                    const showStats = additions > 0 || deletions > 0;

                                    return showStats && (
                                      <div className="flex items-center gap-1 text-xs shrink-0">
                                        {additions > 0 && (
                                          <span className="text-green-500">+{additions}</span>
                                        )}
                                        {deletions > 0 && (
                                          <span className="text-red-500">-{deletions}</span>
                                        )}
                                      </div>
                                    );
                                  })()}
                                </div>
                              </div>
                            </div>
                          </button>
                        );
                      })
                    ) : (
                      <div className="flex flex-col items-center justify-center p-4 text-center">
                        <div className="w-12 h-12 mb-2 text-bolt-elements-textTertiary">
                          <div className="i-ph:file-dashed" />
                        </div>
                        <p className="text-sm font-medium text-bolt-elements-textPrimary">
                          {searchQuery ? 'No matching files' : 'No modified files'}
                        </p>
                        <p className="text-xs text-bolt-elements-textTertiary mt-1">
                          {searchQuery ? 'Try another search' : 'Changes will appear here as you edit'}
                        </p>
                      </div>
                    )}
                  </div>
                </div>

                {hasChanges && (
                  <div className="border-t border-bolt-elements-borderColor p-2">
                    <button
                      onClick={() => {
                        navigator.clipboard.writeText(
                          filteredFiles.map(([filePath]) => filePath).join('\n')
                        );
                        toast('File list copied to clipboard', { 
                          icon: <div className="i-ph:check-circle text-accent-500" /> 
                        });
                      }}
                      className="w-full flex items-center justify-center gap-2 px-3 py-1.5 text-sm rounded-lg bg-bolt-elements-background-depth-1 hover:bg-bolt-elements-background-depth-3 transition-colors text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary"
                    >
                      Copy File List
                    </button>
                  </div>
                )}
              </Popover.Panel>
            </Transition>
          </>
        )}
      </Popover>
    </div>
  );
});

export const Workbench = memo(({ 
  chatStarted, 
  isStreaming,
  actionRunner,
  metadata,
  updateChatMestaData
}: WorkspaceProps) => {
  renderLogger.trace('Workbench');

  const [isSyncing, setIsSyncing] = useState(false);
  const [isPushDialogOpen, setIsPushDialogOpen] = useState(false);
  const [fileHistory, setFileHistory] = useState<Record<string, FileHistory>>({});

  const modifiedFiles = Array.from(useStore(workbenchStore.unsavedFiles).keys());

  const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
  const showWorkbench = useStore(workbenchStore.showWorkbench);
  const selectedFile = useStore(workbenchStore.selectedFile);
  const currentDocument = useStore(workbenchStore.currentDocument);
  const unsavedFiles = useStore(workbenchStore.unsavedFiles);
  const files = useStore(workbenchStore.files);
  const selectedView = useStore(workbenchStore.currentView);

  const isSmallViewport = useViewport(1024);

  const setSelectedView = (view: WorkbenchViewType) => {
    workbenchStore.currentView.set(view);
  };

  useEffect(() => {
    if (hasPreview) {
      setSelectedView('preview');
    }
  }, [hasPreview]);

  useEffect(() => {
    workbenchStore.setDocuments(files);
  }, [files]);

  const onEditorChange = useCallback<OnEditorChange>((update) => {
    workbenchStore.setCurrentDocumentContent(update.content);
  }, []);

  const onEditorScroll = useCallback<OnEditorScroll>((position) => {
    workbenchStore.setCurrentDocumentScrollPosition(position);
  }, []);

  const onFileSelect = useCallback((filePath: string | undefined) => {
    workbenchStore.setSelectedFile(filePath);
  }, []);

  const onFileSave = useCallback(() => {
    workbenchStore.saveCurrentDocument().catch(() => {
      toast.error('Failed to update file content');
    });
  }, []);

  const onFileReset = useCallback(() => {
    workbenchStore.resetCurrentDocument();
  }, []);

  const handleSyncFiles = useCallback(async () => {
    setIsSyncing(true);

    try {
      const directoryHandle = await window.showDirectoryPicker();
      await workbenchStore.syncFiles(directoryHandle);
      toast.success('Files synced successfully');
    } catch (error) {
      console.error('Error syncing files:', error);
      toast.error('Failed to sync files');
    } finally {
      setIsSyncing(false);
    }
  }, []);

  const handleSelectFile = useCallback((filePath: string) => {
    workbenchStore.setSelectedFile(filePath);
    workbenchStore.currentView.set('diff');
  }, []);

  return (
    chatStarted && (
      <motion.div
        initial="closed"
        animate={showWorkbench ? 'open' : 'closed'}
        variants={workbenchVariants}
        className="z-workbench"
      >
        <div
          className={classNames(
            'fixed top-[calc(var(--header-height)+1.5rem)] bottom-6 w-[var(--workbench-inner-width)] mr-4 z-0 transition-[left,width] duration-200 bolt-ease-cubic-bezier',
            {
              'w-full': isSmallViewport,
              'left-0': showWorkbench && isSmallViewport,
              'left-[var(--workbench-left)]': showWorkbench,
              'left-[100%]': !showWorkbench,
            },
          )}
        >
          <div className="absolute inset-0 px-2 lg:px-6">
            <div className="h-full flex flex-col bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor shadow-sm rounded-lg overflow-hidden">
              <div className="flex items-center px-3 py-2 border-b border-bolt-elements-borderColor">
                <Slider selected={selectedView} options={sliderOptions} setSelected={setSelectedView} />
                <div className="ml-auto" />
                {selectedView === 'code' && (
                  <div className="flex overflow-y-auto">
                    <PanelHeaderButton
                      className="mr-1 text-sm"
                      onClick={() => {
                        workbenchStore.downloadZip();
                      }}
                    >
                      <div className="i-ph:code" />
                      Download Code
                    </PanelHeaderButton>
                    <PanelHeaderButton className="mr-1 text-sm" onClick={handleSyncFiles} disabled={isSyncing}>
                      {isSyncing ? <div className="i-ph:spinner" /> : <div className="i-ph:cloud-arrow-down" />}
                      {isSyncing ? 'Syncing...' : 'Sync Files'}
                    </PanelHeaderButton>
                    <PanelHeaderButton
                      className="mr-1 text-sm"
                      onClick={() => {
                        workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
                      }}
                    >
                      <div className="i-ph:terminal" />
                      Toggle Terminal
                    </PanelHeaderButton>
                    <PanelHeaderButton className="mr-1 text-sm" onClick={() => setIsPushDialogOpen(true)}>
                      <div className="i-ph:git-branch" />
                      Push to GitHub
                    </PanelHeaderButton>
                  </div>
                )}
                {selectedView === 'diff' && (
                  <FileModifiedDropdown
                    fileHistory={fileHistory}
                    onSelectFile={handleSelectFile}
                  />
                )}
                <IconButton
                  icon="i-ph:x-circle"
                  className="-mr-1"
                  size="xl"
                  onClick={() => {
                    workbenchStore.showWorkbench.set(false);
                  }}
                />
              </div>
              <div className="relative flex-1 overflow-hidden">
                <View
                  initial={{ x: '0%' }}
                  animate={{ x: selectedView === 'code' ? '0%' : '-100%' }}
                >
                  <EditorPanel
                    editorDocument={currentDocument}
                    isStreaming={isStreaming}
                    selectedFile={selectedFile}
                    files={files}
                    unsavedFiles={unsavedFiles}
                    fileHistory={fileHistory}
                    onFileSelect={onFileSelect}
                    onEditorScroll={onEditorScroll}
                    onEditorChange={onEditorChange}
                    onFileSave={onFileSave}
                    onFileReset={onFileReset}
                  />
                </View>
                <View
                  initial={{ x: '100%' }}
                  animate={{ x: selectedView === 'diff' ? '0%' : selectedView === 'code' ? '100%' : '-100%' }}
                >
                  <DiffView 
                    fileHistory={fileHistory}
                    setFileHistory={setFileHistory}
                    actionRunner={actionRunner}
                  />
                </View>
                <View
                  initial={{ x: '100%' }}
                  animate={{ x: selectedView === 'preview' ? '0%' : '100%' }}
                >
                  <Preview />
                </View>
              </div>
            </div>
          </div>
        </div>
        <PushToGitHubDialog
          isOpen={isPushDialogOpen}
          onClose={() => setIsPushDialogOpen(false)}
          onPush={async (repoName, username, token) => {
            try {
              const commitMessage = prompt('Please enter a commit message:', 'Initial commit') || 'Initial commit';
              await workbenchStore.pushToGitHub(repoName, commitMessage, username, token);
              const repoUrl = `https://github.com/${username}/${repoName}`;
              
              if (updateChatMestaData && !metadata?.gitUrl) {
                updateChatMestaData({
                  ...(metadata || {}),
                  gitUrl: repoUrl,
                });
              }
              
              return repoUrl;
            } catch (error) {
              console.error('Error pushing to GitHub:', error);
              toast.error('Failed to push to GitHub');
              throw error;
            }
          }}
        />
      </motion.div>
    )
  );
});

// View component for rendering content with motion transitions
interface ViewProps extends HTMLMotionProps<'div'> {
  children: JSX.Element;
}

const View = memo(({ children, ...props }: ViewProps) => {
  return (
    <motion.div className="absolute inset-0" transition={viewTransition} {...props}>
      {children}
    </motion.div>
  );
});
